package master.userInterface;
import java.awt.*;
import java.awt.event.*;

import javax.swing.*;

import master.Qubit;
import master.event.ComplexNumberEvent;
import master.event.ComplexNumberListener;
import master.event.RegisterEvent;
import master.event.RegisterEventListener;

/**
 * Produces Bloch sphere representation of a qubit
 * @author Luke Dobinson
 *
 */

public class QubitPanel extends ComplexNumberPanel implements ActionListener,RegisterEventListener{
	
	float cameraPhi = (float) 0, cameraTheta=(float) (Math.PI/4);
	float sphereRadius = 1;
	JButton [] buttons = new JButton[]{
			new JButton("Measure Qubit")};
	
	public QubitPanel(Component parent, Qubit myN, float scale, boolean editable) {
		super(parent, myN, scale, editable);
		myN.getRegister().addRegisterListener(this);
		for(JButton b : buttons){
			this.add(b);
			b.addActionListener(this);
		}
		
	}
	public void paintComponent(Graphics g){
		g.setColor(backgroundColor);
		g.fillRect(0, 0, getWidth(), getHeight());
		int x = this.getWidth()/2;
		int y = this.getHeight()/2;
		if(antialiased)
			((Graphics2D)g).setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		int real = (int)(myN.getReal()/myN.magnitude().getReal() * 255f);
		int im = (int)(myN.getImaginary()/myN.magnitude().getReal() * 255f);
		g.setColor(new Color(0,0,255,real ));
		((Graphics2D)g).scale(10, 10);
		g.drawString("1", x/10-3,  y/10+5);
		g.setColor(new Color(255,0,0,im ));
		g.drawString("0", x/10-3, y/10+5);
		((Graphics2D)g).scale(0.1, 0.1);
		
		//int maxStep = (int) (getWidth()/scale);
		
		
		int dim = ((int) scale);
		g.setColor(lineColor);
		g.drawOval(x-dim, y-dim, 2*dim, 2*dim);
		
		
		/**
		 * Here goes the spherical stuff
		 */
		
		g.setColor(lineColor);
		int baseHeightDIV2 = (int) (Math.abs(scale*Math.cos(cameraTheta)*sphereRadius));
		int baseWidthDIV2  = (int) (Math.abs(scale*sphereRadius));
		
		g.drawOval(x-baseWidthDIV2, y-baseHeightDIV2, baseWidthDIV2*2, 2*baseHeightDIV2);
		
		g.drawLine(x, 0, x, this.getHeight());
		
		g.setColor(lineColor);//draw the axes
		g.drawLine(x, y, x, y - (int)(scale * sphereRadius * Math.sin(cameraTheta)));
		g.drawLine(x, y, x+(int)(scale* Math.cos(cameraPhi)), y-(int)(scale* Math.sin(cameraPhi)*Math.cos(cameraTheta)));
		g.drawLine(x, y, x+(int)(scale* Math.cos(cameraPhi-Math.PI/2)), y-(int)(scale* Math.sin(cameraPhi-Math.PI/2)*Math.cos(cameraTheta)));
		
		
		//draw the state
		g.setColor(vectorColor);
		float [] bitTheta = ((Qubit)myN).get3DTheta();
		float [] bitPhi = ((Qubit)myN).get3DPhi();
		float [] bitR = ((Qubit)myN).get3DR();
		int [] coords;
		for(int i = 0 ; i < bitTheta.length ; i++){
			float bT = bitTheta[i];
			float bP = bitPhi[i];
			float bR = bitR[i];
			coords = sphericalToDraw(bT,bP,bR);
			g.drawLine(x, y, x+coords[0], y+coords[1]);
			g.drawOval(x+coords[0]-5, y+coords[1]-5, 10, 10);
		}
		
		//draw axes gides
		g.setColor(stringColor);
		coords = sphericalToDraw((float)(Math.PI/2),0.1f,sphereRadius);
		g.drawString("x", x+coords[0], y+coords[1]);
		coords = sphericalToDraw((float)(Math.PI/2),(float)(Math.PI/2),sphereRadius);
		g.drawString("y", x+coords[0], y+coords[1]);
		coords = sphericalToDraw(0,0,sphereRadius);
		g.drawString("z", x+coords[0], y+coords[1]);
		
		coords = sphericalToDraw(0,0,sphereRadius+0.2f);
		g.drawString("|0>", x+coords[0], y+coords[1]);
		coords = sphericalToDraw((float)Math.PI,0,sphereRadius+0.2f);
		g.drawString("|1>", x+coords[0], y+coords[1]);
		
	}
	public void actionPerformed(ActionEvent arg0) {
		if(arg0.getActionCommand()=="Measure Qubit"){
			((Qubit)myN).measure();
		}
	}
	float savedTheta;
	float savedPhi;
	
	public int [] sphericalToDraw(float theta , float phi, float r){
		
		float z = (float) (r * Math.cos(theta));
		float x = (float) (r * Math.cos(phi) * Math.sin(theta));
		float y = (float) (r * Math.sin(phi) * Math.sin(theta));
		
		//{radial component, vertical component}
		
		float [] camZ = new float[]{
				(float) -(Math.cos(cameraTheta)*Math.cos(cameraPhi)),
				(float) (Math.cos(cameraTheta)*Math.sin(cameraPhi)),
				(float) Math.sin(cameraTheta)};
		float [] camX = new float[]{ (float)Math.sin(cameraPhi), (float)Math.cos(cameraPhi),0};
		
		float [] coords = new float[]{  camX[0]*x+camX[1]*y+camX[2]*z,  camZ[0]*x+camZ[1]*y+camZ[2]*z};
		coords[0] *= scale;
		coords[1] *= scale;
		return new int[]{(int) coords[0],(int) -coords[1]};
	}
	
	public void mouseDragged(MouseEvent arg0) {
			float xDist = (mouseX - arg0.getX()); xDist /= scale;
			float yDist = (mouseY - arg0.getY()); yDist /= scale;
			
			cameraTheta = yDist + savedTheta;
			if(cameraTheta > Math.PI/2) cameraTheta = (float) Math.PI/2;
			if(cameraTheta < 0) cameraTheta = (float) 0;
			
			cameraPhi = -xDist + savedPhi;
			
			repaint();
			if(arg0.isControlDown()){
				float previousDistFromCentre =
					(float) Math.hypot(pixToReal(mouseX,mouseY)[0],pixToReal(mouseX,mouseY)[1]);
				
				float distFromCentre = 
					(float) Math.hypot(pixToReal(arg0.getX(),arg0.getY())[0],pixToReal(arg0.getX(),arg0.getY())[1]);
				
				//float minimumScale = 0.05f;//any less will kill the program!
				//if((savedScale * (distFromCentre/previousDistFromCentre))>=minimumScale){
				scale = savedScale*(distFromCentre/previousDistFromCentre);
				/*}else{
					scale = minimumScale;
				}*/
				repaint();
			}
	}
	
	public void mousePressed(MouseEvent arg0) {
		savedTheta = cameraTheta;
		savedPhi = cameraPhi;
		savedScale = scale;
	}
	
	public void registerEvent(RegisterEvent e) {
		repaint();
	}
}
